package com.thebitstream.sandy;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;

import org.red5.server.api.service.IServiceCapableConnection;

import com.thebitstream.sandy.support.ClientData;
import com.thebitstream.sandy.support.Engine;
import com.thebitstream.sandy.support.EventService;
import com.thebitstream.sandy.support.PropertyService;

/**
 * @author Andy Shaules
 */
public class Application extends MultiThreadedApplicationAdapter {

	private static Map<String, Engine> rooms = new HashMap<String, Engine>();

	private static long _id = 0;

	private static synchronized String getID() {
		return "sandyClient.".concat(String.valueOf(++_id));
	}

	private int granularity = 100;

	private int timeout=60000;

	/**
	 * Stream packet interval.
	 * @param val
	 */
	public void setGranularity(int val) {
		granularity = val;
	}
	/**
	 * Retry timeout.
	 * @param val
	 */
	public void setTimeout(int val) {
		timeout = val;
	}

	private IScope _lobby;

	@Override
	public boolean appStart(IScope app) {
		_lobby = app;
		Engine.granularity = granularity;
		Engine.timeout = timeout;
		Engine engine = new Engine(app);
		engine.start();
		_lobby.registerServiceHandler("property", new PropertyService());
		_lobby.registerServiceHandler("event", new EventService());
		return true;
	}

	public boolean appConnect(IConnection client, Object[] params) {

		client.getClient().setAttribute(Engine.ID, getID());
		client.getClient().setAttribute(Engine.DATA, new ClientData());
		((ClientData) client.getClient().getAttribute(Engine.DATA)).setId((String) client.getClient().getAttribute(
				Engine.ID));

		if (params.length > 0) {
			((ClientData) client.getClient().getAttribute(Engine.DATA)).data.put("connectionParams", params);
		}
		return true;
	}

	@Override
	public boolean roomStart(IScope room) {
		room.registerServiceHandler("property", new PropertyService());
		room.registerServiceHandler("event", new EventService());
		Engine engine = new Engine(room);
		engine.start();
		rooms.put(room.getContextPath(), engine);
		return true;
	}

	@Override
	public void roomStop(IScope room) {
		room.unregisterServiceHandler("property");
		room.unregisterServiceHandler("event");
		Engine emptyRoom = rooms.remove(room.getContextPath());
		emptyRoom.kill();
	}

	@Override
	public boolean roomJoin(IClient conn, IScope room) {

		IConnection cin = conn.getConnections().iterator().next();

		Collection<Set<IConnection>> clients = room.getConnections();

		for (Set<IConnection> connectionSet : clients) {
			for (final IConnection connection : connectionSet) {

				((IServiceCapableConnection) cin).invoke("onJoin", new Object[] { connection.getClient().getAttribute(
						Engine.DATA) });
				if (Red5.getConnectionLocal() != connection)
					((IServiceCapableConnection) connection).invoke("onJoin", new Object[] { conn
							.getAttribute(Engine.DATA) });
			}
		}
		return true;
	}

	@Override
	public void roomLeave(IClient conn, IScope room) {

		IConnection cin = null;
		try {
			cin = conn.getConnections().iterator().next();
		} catch (Exception e) {
		}

		Collection<Set<IConnection>> clients = room.getConnections();

		for (Set<IConnection> connectionSet : clients) {
			for (final IConnection connection : connectionSet) {

				if (cin != null)
					((IServiceCapableConnection) cin).invoke("onLeave", new Object[] { connection.getClient()
							.getAttribute(Engine.DATA) });

				if (Red5.getConnectionLocal() != connection)
					((IServiceCapableConnection) connection).invoke("onLeave", new Object[] { conn
							.getAttribute(Engine.DATA) });

			}
		}
		//clear any pending call;
		rooms.get(room.getContextPath()).removeClient((ClientData) conn.getAttribute(Engine.DATA));
	}

}
